SwiftUIでPageControlの色を1ページ毎変更する方法
SwiftUIでUIPageViewController
のようなページめくりのViewを作成した際に、そのPageControl
の色を1ページ毎変更する方法を調べました。
環境
- Xcode 14
ページめくりのViewを作成する
SwiftUIでUIPageViewController
のようなページめくりを表現するには、TabView
とiOS 14から使用できる.tabViewStyle
にPageTabViewStyle
を組み合わせることによって簡単に表現することが出来ます。
struct ContentView: View { @State private var selection = 0 var body: some View { TabView(selection: $selection) { Group { Text("0") .tag(0) Text("1") .tag(1) Text("2") .tag(2) } .font(.largeTitle) .foregroundColor(.white) } .background(.black) .tabViewStyle(.page(indexDisplayMode: .automatic)) } }
PageTabViewSyle
.tabViewStyle
にPageTabViewSyle
を設定することでページスクロールするTabView
を表現することが出来ます。
また、.page(indexDisplayMode:)
でPageTabViewStyle.IndexDisplayMode
を指定すると、インデックスの表示モードを変更出来ます。
PageTabViewStyle.IndexDisplayMode
- always
- ページ数に関係なく常にインデックス ビューを表示する
- automatic
- 複数のページがある場合はインデックス ビューを表示します
- never
- インデックス ビューを表示しない
PageControlのcurrentPageIndicatorTintColorがデフォルトで白という問題
簡単にページめくりの表現は出来たのですが、PageControl
のcurrentPageIndicatorTintColor
がデフォルトで白色という問題が出てきました。
背景が白色のViewの場合はPageControl
がほとんど見えないです。
PageControlの色を変更する
調べたところ、現状ではPageControl
の色を変更するには、UIPageControl
のUIAppearance
の値を直接変更する必要がありそうでした。
struct ContentView: View { @State private var selection = 0 // 初期化時にUIPageControlの色を変更 init() { let currenTintColor = UIColor.black UIPageControl.appearance().currentPageIndicatorTintColor = currenTintColor UIPageControl.appearance().pageIndicatorTintColor = currenTintColor.withAlphaComponent(0.2) } var body: some View { TabView(selection: $selection) { Group { Text("0") .tag(0) Text("1") .tag(1) Text("2") .tag(2) } .font(.largeTitle) } .tabViewStyle(.page(indexDisplayMode: .automatic)) } }
初期化時に色を変更
init() { let currenTintColor = UIColor.black UIPageControl.appearance().currentPageIndicatorTintColor = currenTintColor UIPageControl.appearance().pageIndicatorTintColor = currenTintColor.withAlphaComponent(0.2) }
初期化時にUIPageControl.appearance()
に変更したい色を渡すことでUIPageControl
の色を変更出来ました。
ただ問題点としては、UIPageControl.appearance()
の値を変更すると、アプリ全体のUIPageControl
の色に影響を与えてしまいます。
PageControlの色を1ページ毎変更する
UIPageControl
のUIAppearance
を変更する方法を用いて、1ページ毎に色を変更する方法を試してみました。
SelectionTypeを作成
先ほどまではselection
にInt
型を使用していましたが、選択されたものによって、foregroundColor
とbackgroundColor
を変更したかったのでSelectionType
というenum
を作成しました。
enum SelectionType: Int, CaseIterable, Identifiable { case first = 0 case second = 1 case third = 2 var id: Int { return self.rawValue } var foregroundColor: Color { switch self { case .first, .third: return .white case .second: return .black } } var backgroundColor: Color { switch self { case .first, .third: return .black case .second: return .white } } }
SelectionViewを作成
渡されたSelectionType
によってUIPageControl.appearance()
を変更したかったのでカスタムViewを作成しました。
struct SelectionView: View { init(type: SelectionType) { self.number = type.id // UIPageControlの色を変更する let currentPageIndicatorTintColor = UIColor(type.foregroundColor) UIPageControl.appearance().currentPageIndicatorTintColor = currentPageIndicatorTintColor UIPageControl.appearance().pageIndicatorTintColor = currentPageIndicatorTintColor.withAlphaComponent(0.2) } let number: Int var body: some View { Text(String(number)) } }
ContentView
selectionType
によって、foregroundColor
とbackgroundColor
を変更しています。
struct ContentView: View { @State private var selectionType: SelectionType = .first var body: some View { TabView(selection: $selectionType) { ForEach(SelectionType.allCases) { type in SelectionView(type: type) .tag(type) .font(.largeTitle) .foregroundColor(type.foregroundColor) } } .tabViewStyle(.page(indexDisplayMode: .automatic)) .background(selectionType.backgroundColor) } }
しかし、init()
で値を更新する方法では、Bindingで色を変更しているわけではない為、背景色が白の際もcurrentPageIndicatorTintColor
が白のままになっていて、よく見えない結果になりました。
対応策
PageControl
部分を自作して、SelectionType
によって色を変更します。
SectionPageControl
struct SelectionPageControl: UIViewRepresentable { @Binding var type: SelectionType let numberOfPages: Int func makeUIView(context: Context) -> UIPageControl { let control = UIPageControl() // PageControl上に表示するページ数を指定する control.numberOfPages = numberOfPages // PageControlの現在のページ control.currentPage = type.id let currentIndicatorTintColor = UIColor(type.foregroundColor) control.currentPageIndicatorTintColor = currentIndicatorTintColor control.pageIndicatorTintColor = currentIndicatorTintColor.withAlphaComponent(0.2) // PageControl自体をタップされてもPageControl自体が動作しないようにする control.isUserInteractionEnabled = false return control } func updateUIView(_ uiView: UIPageControl, context: Context) { // PageControlの現在のページ uiView.currentPage = type.id let currentIndicatorTintColor = UIColor(type.foregroundColor) uiView.currentPageIndicatorTintColor = currentIndicatorTintColor uiView.pageIndicatorTintColor = currentIndicatorTintColor.withAlphaComponent(0.2) } }
selectionType
をBindingして、その値の変更をcurrentPageIndicatorTintColor
に適用しています。
不要になったUIPageControl.appearanceを消す
struct SelectionView: View { init(type: SelectionType) { self.number = type.id // 不要になった為、消す // let currentPageIndicatorTintColor = UIColor(type.foregroundColor) // UIPageControl.appearance().currentPageIndicatorTintColor = currentPageIndicatorTintColor // UIPageControl.appearance().pageIndicatorTintColor = currentPageIndicatorTintColor.withAlphaComponent(0.2) } let number: Int var body: some View { Text(String(number)) } }
ContentViewに自作PageControlを設置する
struct ContentView: View { @State private var selectionType: SelectionType = .first var body: some View { // VStackで囲む VStack { TabView(selection: $selectionType) { ForEach(SelectionType.allCases) { type in SelectionView(type: type) .tag(type) .font(.largeTitle) .foregroundColor(type.foregroundColor) } } // TabView側のPageControlを.neverに変更 .tabViewStyle(.page(indexDisplayMode: .never)) // 自作PageControlを設置 SelectionPageControl(type: $selectionType, numberOfPages: SelectionType.allCases.count) } // VStackのbackgroundを変更 .background(selectionType.backgroundColor) } }
SelectionPageControl
を設置して、ページ数とバインディングするSelectionType
を渡します。TabView
側のPageControl
は不要になる為、indexDisplayMode
を.never
に変更します。- 自作
PageControl
を画面下部に配置する為にVStack
でTabView
とSelectionPageControl
を囲みます VStack
のbackgroundColor
を希望のカラーの変更します。
結果
無事にページ毎にPageControl
の色を変更することが出来ました!
おわりに
今回はTabView
を使う前提でUIPageControl
を自作する方法で対応しましたが、UIPageViewController
をUIViewControllerRepresentable
で表現する方法もあるかなと思いました。
できれば力技で表現する方法は避けたいのでTabView
のindexDisplay
のtintColor
を気軽に変更できるようなAPIが追加される日を楽しみにしております。
より良い変更方法がありましたら、優しく教えていただけると幸いです。